﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EffiProz.Core.Lib;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Indexes;
using EffiProz.Core.Errors;
using EffiProz.Core.Persist;

namespace EffiProz.Core
{
    public class ExpressionArithmetic : Expression
    {

        public ExpressionArithmetic(int type, Expression left, Expression right)
            : base(type)
        {

            nodes = new Expression[BINARY];
            nodes[LEFT] = left;
            nodes[RIGHT] = right;

            switch (opType)
            {

                case OpTypes.ADD:
                case OpTypes.SUBTRACT:
                case OpTypes.MULTIPLY:
                case OpTypes.DIVIDE:
                case OpTypes.CONCAT:
                    return;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
            }
        }

        public ExpressionArithmetic(int type, Expression e)
            : base(type)
        {



            nodes = new Expression[UNARY];
            nodes[LEFT] = e;

            switch (opType)
            {

                case OpTypes.NEGATE:
                    return;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
            }
        }

        public override  String getSQL()
        {

            StringBuilder sb = new StringBuilder(64);

            switch (opType)
            {

                case OpTypes.VALUE:
                    if (valueData == null)
                    {
                        return Tokens.T_NULL;
                    }

                    if (dataType == null)
                    {
                        throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
                    }

                    return dataType.convertToSQLString(valueData);
            }

            String left = getContextSQL(nodes.Length > 0 ? nodes[LEFT]
                                                          : null);
            String right = getContextSQL(nodes.Length > 1 ? nodes[RIGHT]
                                                          : null);

            switch (opType)
            {

                case OpTypes.CAST:
                    sb.Append(' ').Append(Tokens.T_CAST).Append('(');
                    sb.Append(left).Append(' ').Append(Tokens.T_AS).Append(' ');
                    sb.Append(dataType.getTypeDefinition());
                    sb.Append(')');
                    break;

                case OpTypes.NEGATE:
                    sb.Append('-').Append(left);
                    break;

                case OpTypes.ADD:
                    sb.Append(left).Append('+').Append(right);
                    break;

                case OpTypes.SUBTRACT:
                    sb.Append(left).Append('-').Append(right);
                    break;

                case OpTypes.MULTIPLY:
                    sb.Append(left).Append('*').Append(right);
                    break;

                case OpTypes.DIVIDE:
                    sb.Append(left).Append('/').Append(right);
                    break;

                case OpTypes.CONCAT:
                    sb.Append(left).Append("||").Append(right);
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
            }

            return sb.ToString();
        }

        public override  String describe(Session session, int blanks)
        {

            StringBuilder sb = new StringBuilder(64);

            sb.Append('\n');

            for (int i = 0; i < blanks; i++)
            {
                sb.Append(' ');
            }

            switch (opType)
            {

                case OpTypes.VALUE:
                    sb.Append("VALUE = ").Append(valueData);
                    sb.Append(", TYPE = ").Append(dataType.getNameString());

                    return sb.ToString();

                case OpTypes.ROW:

                //
                case OpTypes.TABLE:
                    sb.Append("VALUELIST ");
                    sb.Append(" TYPE = ").Append(dataType.getNameString());

                    for (int i = 0; i < nodes.Length; i++)
                    {
                        sb.Append(nodes[i].describe(session, blanks + blanks));
                        sb.Append(' ');
                    }
                    break;

                case OpTypes.NEGATE:
                    sb.Append("NEGATE ");
                    break;

                case OpTypes.ADD:
                    sb.Append("ADD ");
                    break;

                case OpTypes.SUBTRACT:
                    sb.Append("SUBTRACT ");
                    break;

                case OpTypes.MULTIPLY:
                    sb.Append("MULTIPLY ");
                    break;

                case OpTypes.DIVIDE:
                    sb.Append("DIVIDE ");
                    break;

                case OpTypes.CONCAT:
                    sb.Append("CONCAT ");
                    break;

                case OpTypes.CAST:
                    sb.Append("CAST ");
                    sb.Append(dataType.getTypeDefinition());
                    sb.Append(' ');
                    break;
            }

            if (nodes[LEFT] != null)
            {
                sb.Append(" arg_left=[");
                sb.Append(nodes[LEFT].describe(session, blanks + 1));
                sb.Append(']');
            }

            if (nodes[RIGHT] != null)
            {
                sb.Append(" arg_right=[");
                sb.Append(nodes[RIGHT].describe(session, blanks + 1));
                sb.Append(']');
            }

            return sb.ToString();
        }

        public override EfzList resolveColumnReferences(RangeVariable[] rangeVarArray,
                int rangeCount, EfzList unresolvedSet, bool acceptsSequences)
        {

            if (opType == OpTypes.VALUE)
            {
                return unresolvedSet;
            }

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] == null)
                {
                    continue;
                }

                unresolvedSet = nodes[i].resolveColumnReferences(rangeVarArray,
                        rangeCount, unresolvedSet, acceptsSequences);
            }

            return unresolvedSet;
        }

        public override void resolveTypes(Session session, Expression parent)
        {

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] != null)
                {
                    nodes[i].resolveTypes(session, this);
                }
            }

            switch (opType)
            {

                case OpTypes.VALUE:
                    break;

                case OpTypes.NEGATE:
                    if (nodes[LEFT]._isParam || nodes[LEFT].dataType == null)
                    {
                        throw Error.error(ErrorCode.X_42567);
                    }

                    dataType = nodes[LEFT].dataType;

                    if (!dataType.isNumberType())
                    {
                        throw Error.error(ErrorCode.X_42565);
                    }

                    if (nodes[LEFT].opType == OpTypes.VALUE)
                    {
                        setAsConstantValue(session);
                    }
                    break;

                case OpTypes.ADD:

                    // special case for concat using +
                    if ((nodes[LEFT].dataType != null && nodes[LEFT].dataType
                            .isCharacterType()) || (nodes[RIGHT].dataType != null
                                                    && nodes[RIGHT].dataType
                                                        .isCharacterType()))
                    {
                        opType = OpTypes.CONCAT;

                        resolveTypesForConcat(session);

                        break;
                    }
                    goto case OpTypes.DIVIDE;

                // fall through
                case OpTypes.SUBTRACT:
                case OpTypes.MULTIPLY:
                case OpTypes.DIVIDE:
                    resolveTypesForArithmetic(session);
                    break;

                case OpTypes.CONCAT:
                    resolveTypesForConcat(session);
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
            }
        }

       public void resolveTypesForArithmetic(Session session)
        {

            if (nodes[LEFT]._isParam && nodes[RIGHT]._isParam)
            {
                throw Error.error(ErrorCode.X_42567);
            }

            if (nodes[LEFT]._isParam)
            {
                nodes[LEFT].dataType = nodes[RIGHT].dataType;
            }
            else if (nodes[RIGHT]._isParam)
            {
                nodes[RIGHT].dataType = nodes[LEFT].dataType;
            }

            if (nodes[LEFT].dataType == null || nodes[RIGHT].dataType == null)
            {
                throw Error.error(ErrorCode.X_42567);
            }

            // datetime subtract - type predetermined
            if (nodes[LEFT].dataType.isDateTimeType()
                    && nodes[RIGHT].dataType.isDateTimeType())
            {
                if (dataType == null)
                {
                    throw Error.error(ErrorCode.X_42566);
                }
                else if (!dataType.isIntervalType()
                         || nodes[LEFT].dataType.typeCode
                            != nodes[RIGHT].dataType.typeCode)
                {
                    throw Error.error(ErrorCode.X_42562);
                }
            }
            else
            {
                dataType =
                    nodes[LEFT].dataType.getCombinedType(nodes[RIGHT].dataType,
                        opType);

                if (dataType.isDateTimeType())
                {
                    if (nodes[LEFT].dataType.isIntervalType())
                    {
                        if (opType != OpTypes.ADD)
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }

                        Expression temp = nodes[LEFT];

                        nodes[LEFT] = nodes[RIGHT];
                        nodes[RIGHT] = temp;
                    }
                }
            }

            if (nodes[LEFT].opType == OpTypes.VALUE
                    && nodes[RIGHT].opType == OpTypes.VALUE)
            {
                setAsConstantValue(session);
            }
        }

        void resolveTypesForConcat(Session session)
        {

            if (dataType != null)
            {
                return;
            }

            if (nodes[LEFT]._isParam)
            {
                nodes[LEFT].dataType = nodes[RIGHT].dataType;
            }
            else if (nodes[RIGHT]._isParam)
            {
                nodes[RIGHT].dataType = nodes[LEFT].dataType;
            }

            if (nodes[LEFT].dataType == null || nodes[RIGHT].dataType == null)
            {
                throw Error.error(ErrorCode.X_42567);
            }

            if (nodes[LEFT].dataType.isBinaryType()
                    ^ nodes[RIGHT].dataType.isBinaryType())
            {
                throw Error.error(ErrorCode.X_42565);
            }

            // conversion of right argument to character for backward compatibility
            if (nodes[LEFT].dataType.isCharacterType()
                    && !nodes[RIGHT].dataType.isCharacterType())
            {
                SqlType newType = CharacterType.getCharacterType(Types.SQL_VARCHAR,
                    nodes[RIGHT].dataType.displaySize());

                nodes[RIGHT] = ExpressionOp.getCastExpression(session,
                        nodes[RIGHT], newType);
            }

            dataType = nodes[LEFT].dataType.getCombinedType(nodes[RIGHT].dataType,
                    OpTypes.CONCAT);

            if (nodes[LEFT].opType == OpTypes.VALUE
                    && nodes[RIGHT].opType == OpTypes.VALUE)
            {
                setAsConstantValue(session);
            }
        }

        public override Object getValue(Session session)
        {

            switch (opType)
            {

                case OpTypes.VALUE:
                    return valueData;

                case OpTypes.SIMPLE_COLUMN:
                    {
                        Object[] data =
                            (Object[])session.sessionContext
                                .rangeIterators[rangePosition].getCurrent();

                        return data[columnIndex];
                    }
                case OpTypes.NEGATE:
                    return ((NumberType)dataType).negate(
                        nodes[LEFT].getValue(session, nodes[LEFT].dataType));
            }

            Object a = nodes[LEFT].getValue(session);
            Object b = nodes[RIGHT].getValue(session);

            switch (opType)
            {

                case OpTypes.ADD:
                    return dataType.add(a, b, nodes[RIGHT].dataType);

                case OpTypes.SUBTRACT:
                    return dataType.subtract(a, b, nodes[RIGHT].dataType);

                case OpTypes.MULTIPLY:
                    return dataType.multiply(a, b);

                case OpTypes.DIVIDE:
                    return dataType.divide(a, b);

                case OpTypes.CONCAT:
                    return dataType.concat(session, a, b);

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
            }
        }
    }
}